-- IOT平台协议模块
-- 2021.05.13
-- Lingd
-- 福州泰诚网络科技有限公司

module(...,package.seeall)

require "misc"
require "nvm"
require "net"
require "sim"
require "socket1"
require "audio"
require "hc595"
require "handle"

local 	HARD_VERSION  		= 	17                  -- 硬件版本
local 	SOFT_VERSION  		= 	20                  -- 软件版本
local 	Ptl_MsgCount  		= 	0                   -- 信息序列号
local 	SYSUUID       		= 	""                  -- 系统UUID
local 	SERLINKFLG     		= 	false               -- 入网请求标志
local 	REQ_ERR_NUM			= 	0					-- 请求包发送次数，达到一定次数后，进行异常处理
local 	HEARD_ERR_NUM		= 	0					-- 监听发送失败次数，达到一定次数后，进行异常处理
local 	HEARD_ERR_COUNT 	= 	0					-- 监听发送总次数，达到一定次数后，进行异常处理
local	HEARD_TIME			=	60*1000				-- 心跳包时间
local	REQ_TIME			= 	5*1000				-- 入网请求重新发起时间

-- 命令字

local	CMD_ACCESSREQ   	=	"11"          		-- 入网请求(失败,服务端直接断开连接)   		（上行）
local	CMD_HEARTBEAT   	=	"13"         		-- 心跳包                             		（上行）
local	CMD_GETHARDVER   	=	"59"         		-- 获取硬件版本号                         	（下行）
local	CMD_RESET     		=	"69"         		-- 软件重启                        			（下行）
local  	CMD_SETDEVID     	=	"75"         		-- 修改设备地址                        		（下行）
local	CMD_UPSTATE			=	"21"				-- 上报端口状态                        		（上行）
local	CMD_UPEND			=	"23"				-- 停止充电上报                        		（上行）
local   CMD_GETSTATE        =   "51"                -- 获取端口状态                             （下行）
local   CMD_OPENSOCKET      =   "55"                -- 按金额打开端口                           （下行）
local   CMD_OPENSOCKET1     =   "53"                -- 按时间打开端口                           （下行）
local   CMD_CLOSESOCKET     =   "57"                -- 关闭端口                                 （下行）
local   CMD_SETCFG     		=   "65"                -- 设置充电参数                             （下行）
local   CMD_GETCFG     		=   "67"                -- 获取充电参数                             （下行）

local  	CMD_ACK_ACCESSREQ	=	"12"           		-- 入网请求回应                        		（下行）    
local   CMD_ACK_HEARTBEAT 	=	"14"           		-- 回应心跳包                            	（下行）
local   CMD_ACK_STATE       =   "22"                -- 回应状态                                 （下行）
local   CMD_ACK_UPEND       =   "24"                -- 回应停止充电上报                       	（下行）
local   CMD_ACK_GETHARDVER  =	"60"         		-- 回应获取硬件版本号                     	（上行）
local   CMD_ACK_SETDEVID   	=	"76"           		-- 回应修改设备地址                 		（上行）
local   CMD_ACK_GETSTATE    =   "52"                -- 回应状态查询                     	    （上行）
local   CMD_ACK_OPENSOCKET  =   "56"                -- 回应按金额打开端口                       （上行）
local   CMD_ACK_OPENSOCKET1 =   "54"                -- 回应按时间打开端口                       （上行）
local   CMD_ACK_CLOSESOCKET =   "58"                -- 回应关闭端口                             （上行）
local   CMD_ACK_SETCFG     	=   "66"                -- 回应设置充电参数                         （上行）
local   CMD_ACK_GETCFG     	=   "68"                -- 回应获取充电参数                         （上行）

-- 语音转义
ChineseTab = {"衣","二","三","四","五","六","七","八"}

function init()
	SYSUUID   		= nvm.get("SysUUID")
    SERLINKFLG      = false
	Ptl_MsgCount    = 0
	SendAccessReq( )                                -- 发送入网请求
end

-- 计算校验和0-99
local function CalcSum(str,start)
  local sum,i = 0
  for i=start,#str do
    sum = sum + string.byte(str,i)-48
  end
  
  return string.format("%02d",sum%100)
end

-- 协议打包函数
function Pack(Cmd,MsgCount,pBuf)
    	                               
end

-- 函数功能: 发送入网请求函数
function SendAccessReq( )
	
	REQ_ERR_NUM = REQ_ERR_NUM + 1
	log.info("REQ_ERR_NUM",REQ_ERR_NUM)

	-- 发送次数超过，则进行异常处理
	if REQ_ERR_NUM > 10 then
		REQ_ERR_NUM = 0
		sys.restart('请求失败，软件重启')							-- 请求失败，软件重启
	end

	-- 未入网成功，则继续发起请求
	if SERLINKFLG == false then 
	
		SYSUUID   	= nvm.get("SysUUID")

		if SYSUUID == ""  then
			-- 模块IMEI串号
			SYSUUID = string.sub(misc.getImei(),-8)
			-- 存入配置文件
			nvm.set("SysUUID",SYSUUID)
		end
		
		Ptl_MsgCount = 0 
		Pack(CMD_ACCESSREQ,Ptl_MsgCount,SYSUUID)		        -- 只有当服务器显示设备不在线时，入网请求才会有回应
		
		sys.timerStart(SendAccessReq,REQ_TIME) 				    -- 发送入网请求，如无响应，5秒后重发
	end
end

-- 函数功能: 发送端口状态函数
function SendPortState( )
    local Cmd = CMD_UPSTATE           				-- 状态上报命令字
    
    -- 入网成功，才状态
    if SERLINKFLG == true then 

		-- 计算消息序号
		Ptl_MsgCount = (Ptl_MsgCount + 1)%99999999

		local i,str = 1,""
		for i = 1,#handle.PortState do 
			str = str..handle.PortState[i]
		end
		
	    -- 发送数据
	    Pack(Cmd,Ptl_MsgCount,string.format("%04d",handle.PORT_NUM)..str)
	end
end

-- 函数功能: 回应端口状态函数
function AckPortState()
    local Cmd = CMD_ACK_GETSTATE				-- 回应端口状态命令字
    
	local i,str = 1,""
	for i = 1,#handle.PortState do 
		str = str..handle.PortState[i]
	end
	
	 -- 发送数据
	 Pack(Cmd,Ptl_MsgCount,string.format("%06d",handle.PORT_NUM)..str)
end

-- 函数功能: 结束充电上报函数
function SendEndChargeInfo(ch,state)
    local Cmd = CMD_UPEND           					-- 停止充电上报命令字
    
	-- 计算消息序号
	Ptl_MsgCount = (Ptl_MsgCount + 1)%99999999

	local datestr = nvm.get("InfoCh"..ch)						-- 获取通道开始充电的日期时间
	local cdtime = math.ceil(handle.CdTime[ch]/60)				-- 计算使用时长（单位：分钟）
		
	-- 发送数据
	Pack(Cmd,Ptl_MsgCount,string.format("%02d%02d%s%03d",ch,state,datestr,cdtime))
end

-- 函数功能: 发送心跳函数
function SendHeart( )
    local Data,Cmd = {},CMD_HEARTBEAT           				-- 心跳命令字
    
    -- 入网成功，才发送心跳
    if SERLINKFLG == true then 

	    HEARD_ERR_COUNT = HEARD_ERR_COUNT + 1               

		-- 计算消息序号
		Ptl_MsgCount = (Ptl_MsgCount + 35)%99999999

	    -- 发送数据
	    Pack(Cmd,Ptl_MsgCount,"")				                -- 由于不使用心跳包，则利用状态包来维持TCP连接
	end
	sys.timerStart(SendHeart,HEARD_TIME) 						-- 发送心跳包，1分钟一次
end

-- 命令解析
function CMD_Unpack( pBuf,Cmd,MsgCount )

	local i = 0

    -- 入网请求回应                   						（下行）
    if Cmd == CMD_ACK_ACCESSREQ then
        log.info("CMD_ACK_ACCESSREQ",tool.TableToString(pBuf,0,#pBuf))
        SERLINKFLG  = true      								-- 收到回应
		REQ_ERR_NUM = 0											-- 清空连接错误计数
        hc595.LED_Show(hc595.DEV_SYS,hc595.LED_GREEN,1000,1000) -- 入网成功，绿灯闪烁，说明系统正常
        hc595.LED_TCP(3)                                        -- 入网成功，亮绿灯
        sys.timerStart(SendPortState,2*1000)                    -- 2庙后发送端口状态
		sys.timerStart(SendHeart,HEARD_TIME) 					-- 1分钟后发送心跳包

    -- 心跳回应                       						（下行）
    elseif Cmd == CMD_ACK_HEARTBEAT then
        log.info("CMD_ACK_HEARTBEAT",tool.TableToString(pBuf,0,#pBuf))
        HEARD_ERR_NUM = 0

	-- 获取硬件版本                       					（下行）
    elseif Cmd == CMD_GETHARDVER then
	    log.info("CMD_GETHARDVER",tool.TableToString(pBuf,0,#pBuf))
		local csq = string.format("%02d",net.getRssi()*(52.0/31.0))
		local ccid = sim.getIccid()
		Pack(CMD_ACK_GETHARDVER,MsgCount,"00"..HARD_VERSION..SOFT_VERSION..csq..ccid.."00000000000000000000")
	
	-- 修改设备地址
    elseif Cmd == CMD_SETDEVID then
		log.info("CMD_SETDEVID",tool.TableToString(pBuf,0,#pBuf))
		SYSUUID = ""
		for i=1,#pBuf-1 do 
			SYSUUID = SYSUUID .. string.char(pBuf[i])
		end
		log.info("SysUUID",SYSUUID)
		nvm.set("SysUUID",SYSUUID)										-- 存入配置文件
		Pack(CMD_ACK_SETDEVID,MsgCount,"00")
		sys.timerStart(sys.restart,2000,'修改设备地址，软件重启') 		-- 修改设备地址，软件重启         
	
    -- 打开端口（按金额开始充电）
    elseif Cmd == CMD_OPENSOCKET then
        log.info("CMD_OPENSOCKET",tool.TableToString(pBuf,0,#pBuf))
        local ch = tool.TableToNumber(pBuf,0,2)
        local rmb = tool.TableToNumber(pBuf,2,4)/100
		local datestr = string.format("%06d%06d",tool.TableToNumber(pBuf,6,6),tool.TableToNumber(pBuf,12,6))
		nvm.set("InfoCh"..ch,datestr)									-- 保存开始充电的时间
        handle.start_charge(ch,handle.RmbTime*rmb)						-- 开启充电
        Pack(CMD_ACK_OPENSOCKET,MsgCount,string.format("%02d%05d",ch,(handle.RmbTime/60))..datestr)
        audio.setStrategy(1)
		audio.play(0,"TTS",ChineseTab[ch].." 路 开始充电，充电时间"..(handle.RmbTime*rmb/60).."分钟",5,nil,false)
		
	-- 打开端口（按时间开始充电）
    elseif Cmd == CMD_OPENSOCKET1 then
        log.info("CMD_OPENSOCKET",tool.TableToString(pBuf,0,#pBuf))
        local ch = tool.TableToNumber(pBuf,0,2)
        local time = tool.TableToNumber(pBuf,2,3)
		local datestr = string.format("%06d%06d",tool.TableToNumber(pBuf,5,6),tool.TableToNumber(pBuf,11,6))
		nvm.set("InfoCh"..ch,datestr)									-- 保存开始充电的时间
        handle.start_charge(ch,time*60)									-- 开启充电(单位：秒)
        Pack(CMD_ACK_OPENSOCKET1,MsgCount,string.format("%02d%05d",ch,(handle.RmbTime/60))..datestr)
        audio.setStrategy(1)
		audio.play(0,"TTS",ChineseTab[ch].." 路 开始充电，充电时间"..time.."分钟",5,nil,false)
    
    -- 结束充电
    elseif Cmd == CMD_CLOSESOCKET then
        log.info("CMD_CLOSESOCKET",tool.TableToString(pBuf,0,#pBuf))
        local ch = tool.TableToNumber(pBuf,0,2)
		local datestr = nvm.get("InfoCh"..ch)							-- 获取通道开始充电的日期时间
		local cdtime = math.ceil(handle.CdTime[ch]/60)					-- 计算使用时长（单位：分钟）
		handle.end_charge(ch)
        Pack(CMD_ACK_CLOSESOCKET,MsgCount,string.format("%02d0%05d",ch,cdtime)..datestr)
		
	-- 结束充电上报回应
    elseif Cmd == CMD_ACK_UPEND then
        log.info("CMD_ACK_UPEND",tool.TableToString(pBuf,0,#pBuf))
		
	-- 获取端口状态
    elseif Cmd == CMD_GETSTATE then
        log.info("CMD_GETSTATE",tool.TableToString(pBuf,0,#pBuf))
		AckPortState()
	
	-- 设置充电参数
    elseif Cmd == CMD_SETCFG then
        log.info("CMD_SETCFG",tool.TableToString(pBuf,0,#pBuf))
		nvm.set("MeuFI",tool.GetPreciseDecimal(tool.TableToNumber(pBuf,40,3)/220,3))
		nvm.set("MeuFTime",tool.TableToNumber(pBuf,43,3)*60)
		nvm.set("MeuMinI",tool.GetPreciseDecimal(tool.TableToNumber(pBuf,46,4)/220,3))
		nvm.set("MeuKTime",tool.TableToNumber(pBuf,54,2)*60)
		nvm.set("MeuMaxI",tool.GetPreciseDecimal(tool.TableToNumber(pBuf,50,4)/220,3))
		nvm.set("RmbTime",tool.TableToNumber(pBuf,9,3)*60)
		
		handle.getcfg()									-- 重新获取配置
		Pack(CMD_ACK_SETCFG,MsgCount,"00")
		
	-- 获取充电参数
    elseif Cmd == CMD_GETCFG then
		log.info("CMD_GETCFG",tool.TableToString(pBuf,0,#pBuf))
		local str = "000000"
		str = str.."000"
		str = str..string.format("%03d",math.ceil(nvm.get("RmbTime")/60)) 		-- 1元的充电时间
		str = str.."200200200"
		str = str.."1000100010001000"
		str = str.."100"
		str = str..string.format("%03d",nvm.get("MeuFI")*220)              		-- 浮充功率
		str = str..string.format("%03d",math.ceil(nvm.get("MeuFTime")/60))	 	-- 浮充时间
		str = str..string.format("%04d",nvm.get("MeuMinI")*220)	            	-- 最小功率
		str = str..string.format("%04d",nvm.get("MeuMaxI")*220)	            	-- 最大功率
		str = str..string.format("%02d",nvm.get("MeuKTime")/60)           		-- 空载时间
		str = str.."000"
		str = str.."0501024"
		str = str.."001"
		
		Pack(CMD_ACK_GETCFG,MsgCount,str)
	-- 软件重启	
    elseif Cmd == CMD_RESET then
		log.info("CMD_RESET")
		sys.timerStart(sys.restart,2000,'后台重启，软件重启') 			-- 后台重启，软件重启         
	end
end

-- 解包函数
function ParsePack( buf,str )
  
end

-- 心跳监控，失败次数达到时，则重启系统，或2分钟内发送心跳的次数超过5次，则重启系统
function HeardMonitor( )
	HEARD_ERR_NUM = HEARD_ERR_NUM + 1

	if HEARD_ERR_NUM >= 5 then											  	-- 每2分钟查询一次
		log.info("HEARD_ERR_NUM")
		HEARD_ERR_NUM = 0
		sys.timerStart(sys.restart,2000,'心跳包失败重启，软件重启') 		-- 后台重启，软件重启
	end

	if HEARD_ERR_COUNT >= 5 then										  	-- 每2分钟查询一次
		log.info("HEARD_ERR_COUNT")
		HEARD_ERR_COUNT = 0
		sys.timerStart(sys.restart,2000,'心跳包超数重启，软件重启') 		-- 后台重启，软件重启
	else
		HEARD_ERR_COUNT = 0
	end
end

sys.timerLoopStart(HeardMonitor,2*60000)    								-- 监听心跳函数,2分钟，超过次数则重启